package com.chrishobson.jreedemo;

import com.chrishobson.jreedemo.utils.DemoBase;
import com.chrishobson.jreeglut.*;
import com.chrishobson.maths.Matrix44;
import com.chrishobson.maths.Vector3D;

import static com.chrishobson.jreeglut.glut.*;
import static com.chrishobson.jreegl.gl.*;

/**
 * First 3d drawing. Cube spins as the window is resized.
 * 
 * 
 * 
 * 
 * @author clh
 * 
 */
public class First3D extends DemoBase implements DisplayHandler,
    KeyboardHandler, SpecialHandler {

  public First3D(String[] a_Args) {
    super("3D");
    m_W.SetDisplayHandler(this);
    m_W.SetKeyboardHandler(this);
    m_W.SetSpecialHandler(this);

    m_Angles = new First3DDirWin(this);
  }

  public void DisplayFunc(DisplayData a_Data) {
    glClearColor(.2, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);

    glColor(1.0, 1.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(m_LookAt.GetI(), m_LookAt.GetJ(), m_LookAt.GetK(), 0, 0, 0,
        m_Up.GetI(), m_Up.GetJ(), m_Up.GetK());

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1, 1, -1, 1, -100, 100);

    glutWireCube(1.0);

    // Red line along X axis
    glColor(1.0, 0.0, 0.0);
    glBegin(GL_LINES);
    glVertex(0, 0, 0);
    glVertex(0.55, 0, 0);
    glEnd();

    // Green line along Y axis
    glColor(0.0, 1.0, 0.0);
    glBegin(GL_LINES);
    glVertex(0, 0, 0);
    glVertex(0, 0.55, 0);
    glEnd();

    // Blue line along Z axis
    glColor(0.0, 0.0, 1.0);
    glBegin(GL_LINES);
    glVertex(0, 0, 0);
    glVertex(0, 0, 0.55);
    glEnd();

    glFlush();
    glutSwapBuffers();
  }

  /**
   * Returns a unit vector which points at the AZ/EL away from the centre of a
   * sphere to the edge.
   * 
   * @param a_Vector
   *          The vector
   */

  void GetAZEL(Vector3D a_Vec) {
    double l_EL = Math.toRadians(m_EL);
    double l_AZ = Math.toRadians(m_AZ);

    /**
     * The vector is generated by first applying a rotation around Y by the
     * elevation to a vector 1,0,0 and the around Z by the azimuth. The right
     * handed matrices are concatted and multipled out by 1,0,0 to give:-
     */
    a_Vec.SetIJK(Math.cos(l_AZ) * Math.cos(l_EL),
        Math.sin(l_AZ) * Math.cos(l_EL), Math.sin(l_EL));
  }

  Vector3D GetLookAt() {
    return m_LookAt;
  }

  Vector3D GetUp() {
    return m_Up;
  }

  public void KeyboardFunc(KeyboardData a_Data) {
    if (a_Data.GetKey() == 27) {
      // ESC is exit, probably should exit glut main loop and drop
      // out normally from main though
      // TODO leave main loop
      System.exit(0);
    } else if (a_Data.GetKey() == glut.GLUT_KEY_LEFT) {
      if(a_Data.IsShift()) {
        RotateUp(-1);
      } else {
        SetAZEL(m_AZ - 1, m_EL);
      }
    } else if (a_Data.GetKey() == glut.GLUT_KEY_RIGHT) {
      if(a_Data.IsShift()) {
        RotateUp(1);
      } else {
        SetAZEL(m_AZ + 1, m_EL);
      }
    } else if (a_Data.GetKey() == glut.GLUT_KEY_UP) {
      SetAZEL(m_AZ, m_EL + 1);
    } else if (a_Data.GetKey() == glut.GLUT_KEY_DOWN) {
      SetAZEL(m_AZ, m_EL - 1);
    }
    a_Data.GetWindow().PostRedisplay();
    m_Angles.Update();

  }

  public void SpecialFunc(KeyboardData a_Data) {
    KeyboardFunc(a_Data);
  }

  private void SetAZEL(double l_AZ, double l_EL) {
    m_AZ = l_AZ;
    m_EL = l_EL;

    GetAZEL(m_LookAt);
    m_Up.MakePerp(m_LookAt);

  }

  public void RotateUp(double angle) {
    m_Mat.make_rotation_vector(m_LookAt, angle);
    m_Mat.transform(m_Up, m_Up);
  }
  
  double m_AZ = 0.0;
  double m_EL = 0;
  Vector3D m_LookAt = new Vector3D(1, 0, 0);
  Vector3D m_Up = new Vector3D(0, 0, 1);
  First3DDirWin m_Angles;
  private Matrix44 m_Mat = new Matrix44();
}

/**
 * This class provides an extra window which shows and Isometric view and shows
 * the orientation of the gluLookAt in the main view. As the main view is
 * rotated this view updates to show the lookat direction and the up vector of
 * the main view.
 * 
 * @author clh
 * 
 */
class First3DDirWin extends DemoBase implements DisplayHandler {
  First3DDirWin(First3D a_3D) {
    super("View Dir");

    m_W.SetDisplayHandler(this);
    m_3D = a_3D;
  }

  public void Update() {
    m_W.PostRedisplay();
  }

  public void DisplayFunc(DisplayData a_Data) {
    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    Vector3D l_LookAt = m_3D.GetLookAt();
    Vector3D l_Up = m_3D.GetUp();
    /*
    System.out.println("Look " + l_LookAt.GetI() + " " + l_LookAt.GetJ() + " "
        + l_LookAt.GetK());
    System.out.println("Up " + l_Up.GetI() + " " + l_Up.GetJ() + " "
        + l_Up.GetK());
    */
    
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    gluLookAt(-1, -1, 1, 0, 0, 0, 0, 0, 1);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.1, 1.1, -1.1, 1.1, -100, 100);

    // Red line along X axis
    glColor(1.0, 0.0, 0.0);
    glBegin(GL_LINES);
    glVertex(0, 0, 0);
    glVertex(0.55, 0, 0);
    glEnd();

    // Green line along Y axis
    glColor(0.0, 1.0, 0.0);
    glBegin(GL_LINES);
    glVertex(0, 0, 0);
    glVertex(0, 0.55, 0);
    glEnd();

    // Blue line along Z axis
    glColor(0.0, 0.0, 1.0);
    glBegin(GL_LINES);
    glVertex(0, 0, 0);
    glVertex(0, 0, 0.55);
    glEnd();

    glColor(0, 1, 1);
    glBegin(GL_LINE_STRIP);
    glVertex(0, 0, 0);
    glVertex(l_LookAt.GetI(), l_LookAt.GetJ(), l_LookAt.GetK());
    glColor(1, 1, 1);
    glVertex(l_LookAt.GetI() + l_Up.GetI(), l_LookAt.GetJ() + l_Up.GetJ(),
        l_LookAt.GetK() + l_Up.GetK());
    glEnd();

    glColor(1, 1, 1);
    glutWireSphere(1, 20, 20);

    glFlush();
    glutSwapBuffers();
  }

  private First3D m_3D;
}
